#ifndef __MY_DATA_SOURCE_
#define __MY_DATA_SOURCE_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#pragma  warning(disable: 4150)
#include <vector>
#include <cmath>
// #include "pca.h"
// #include "KMeans.h"

template<typename T>
class CPCA;

template<typename T>
class CKMeans;

template<typename T>
class CDataSource  
{
public:
	int m_Dim;
	int m_nSamples;

	CPCA<T> *m_pPCA;
	std::vector<CKMeans<T>* > m_pKMeans;

public:
	CDataSource(int nSamples, int dim)
		:m_Dim(dim),m_nSamples(nSamples),m_pPCA(NULL){}
	virtual ~CDataSource()
	{
		delete m_pPCA;
		for(unsigned i=0; i<m_pKMeans.size(); i++){
			delete m_pKMeans[i];
		}
	}

	virtual void getData(int idx, T *const dst) const = 0;
	virtual double squareDist(int idx1, int idx2) const
	{
		std::vector<T> p1(m_Dim), p2(m_Dim);
		getData(idx1, &p1.front());
		getData(idx2, &p2.front());
		double dist=0;
		for(int i=0; i<m_Dim; i++)
			dist += (p1[i]-p2[i])*(p1[i]-p2[i]);
		
		return dist;
	}
	virtual double weightedSquareDist(const T *const p1, const T *const  p2, const int *const weight) const
	{
		double dist=0;
		for(int i=0; i<m_Dim; i++)
			dist += (p1[i]-p2[i])*(p1[i]-p2[i])*(1<<weight[i]);

		return dist;
	}
	virtual double weightedSquareDist(int idx, const T *const  p2, const int *const weight) const
	{
		std::vector<T> p1(m_Dim);
		getData(idx, &p1.front());
		return weightedSquareDist(&p1.front(), p2, weight);
	}
	virtual double squareDist(const T *const p1, const T *const  p2) const
	{
		double dist=0;
		for(int i=0; i<m_Dim; i++)
			dist += (p1[i]-p2[i])*(p1[i]-p2[i]);

		return dist;
	}
	virtual double squareDist(int idx, const T *const  pp) const
	{
		std::vector<T> p1(m_Dim);
		getData(idx, &p1.front());
		return squareDist(&p1.front(), pp);
	}

	// searching accelerate preprocess
	bool preSearch(int pcaDim, int kmeank, int kmeanIter, bool replaceCenter)
	{
		m_pKMeans.resize(1, NULL);
		if(pcaDim>0 && pcaDim<m_Dim){
			m_pPCA = new CPCA<T>(this, pcaDim, true);
			m_pKMeans[0] = new CKMeans<T>(m_pPCA->m_pResult, kmeank);
		}
		else{
			m_pKMeans[0] = new CKMeans<T>(this, kmeank);
		}
		m_pKMeans[0]->kMeans(kmeanIter, replaceCenter);

		return true;
	}

// 	bool preSearchHierach(int pcaDim, int nLevel, int kmeanIter, bool replaceCenter)
// 	{
// 		const int kmeank = pow(m_nSamples, nLevel)+1;
// 		if(pcaDim>0 && pcaDim<m_Dim){
// 			m_pPCA = new CPCA<T>(this, pcaDim, true);
// 			m_pKMeans.push_back(new CKMeans<T>(m_pPCA->m_pResult, kmeank));
// 		}
// 		else{
// 			m_pKMeans.push_back(new CKMeans<T>(this, kmeank));
// 		}
// 		m_pKMeans[0]->kMeans(kmeanIter, replaceCenter);
// 
// 		return true;
// 	}

	int search(const T *const queryPt, int* weight=NULL)
	{
		if(m_pPCA){
			std::vector<T> pt(m_Dim);
			m_pPCA->ConvertPoint(queryPt, &pt.front());
			return m_pKMeans[0]->Search(&pt.front(), weight);
		}

		return m_pKMeans[0]->Search(queryPt, weight);
	}

	int exhaustSearch(const T *const queryPt)
	{
		return m_pKMeans[0]->ExhaustSearch(queryPt);
	}
};

template<typename T>
class CSimDataSource: public CDataSource<T>
{
private:
	const T *const * m_pSource;
	T** m_pSrcBak;
public:
	~CSimDataSource()
	{
		if(m_pSrcBak){
			for(int i=0; i<m_nSamples; i++){
				delete [] m_pSrcBak[i];
			}
			delete [] m_pSrcBak;
		}
	}
	CSimDataSource(const T *const *const pSource, int nSamples, int dim, bool copysrc = false)
		:CDataSource<T>(nSamples, dim), m_pSource(pSource), m_pSrcBak(NULL)
	{
		if(m_pSource == NULL){
			preCopySrc();
			m_pSource = m_pSrcBak;
		}
		else if(copysrc){
			preCopySrc();
			backupSrc();
		}
	}

	T* getData(int idx)
	{
		return m_pSrcBak[idx];
	}

	void getData(int idx, T *const dst) const
	{
		std::copy(m_pSource[idx], m_pSource[idx]+m_Dim, dst);
	}
	
	void preCopySrc()
	{
		m_pSrcBak = new T* [m_nSamples];
		for(int i=0; i<m_nSamples; i++){
			m_pSrcBak[i] = new T[m_Dim];
		}
	}

	void backupSrc()
	{
		for(int i=0; i<m_nSamples; i++){
			std::copy(m_pSource[i], m_pSource[i]+m_Dim, m_pSrcBak[i]);
		}
		m_pSource = m_pSrcBak;
	}
};

template<typename T>
class CImgDataSource: public CDataSource<T>
{
public:
	const T *const *const m_pSource;
	int m_ImgDim;
	int m_Width, m_Height;
	int m_nPtsX, m_nPtsY;

	std::vector<int> m_Mask;

	const bool m_Tileable;
public:
	CImgDataSource(const T *const *const pSouce, int nSamples, int dim, 
		int width, int height, int imgDim, bool tileable, 
		int nPtsx, int nPtsy, const std::vector<int> &mask)
		:m_pSource(pSouce), m_ImgDim(imgDim), m_Width(width), m_Height(height), m_Tileable(tileable),
		m_nPtsX(nPtsx),m_nPtsY(nPtsy),m_Mask(mask), CDataSource<T>(nSamples, dim),
		m_pTrGetData(tileable?(&CImgDataSource<T>::getDataTile):(&CImgDataSource<T>::getDataInner))
	{
		assert(m_nPtsX*m_nPtsY == m_nSamples);
		assert(m_Mask.size()*m_ImgDim == m_Dim*2 && m_Dim>0);
		if(m_nPtsX*m_nPtsY != m_nSamples || m_Dim<=0 || m_Mask.size()*m_ImgDim != m_Dim*2)
			throw std::invalid_argument("Invalid argument!");
	}

	void getData(int idx, T *const dst) const
	{
		assert(idx>=0 && idx<m_nSamples);
		getData(idx%m_nPtsX, idx/m_nPtsX, dst);
	}

	void getData(int x0, int y0, T *const dst) const
	{
		(this->*m_pTrGetData)(x0, y0, dst);
/*		assert(x0>=0 && x0<m_nPtsX && y0>=0 && y0<m_nPtsY);
		const int nMaskPts = m_Mask.size()/2;
		std::vector<int>::const_iterator x=m_Mask.begin();
		std::vector<int>::const_iterator y=m_Mask.begin()+nMaskPts;
		for(int i=0; i<nMaskPts; i++){
			int idx = (y0+y[i])%m_Height*m_Width + (x0+x[i])%m_Width;
			std::copy(m_pSource[idx], m_pSource[idx]+m_ImgDim, dst+i*m_ImgDim);
		}
*/	}

private:
	void (CImgDataSource<T>::*const m_pTrGetData)(int x, int y, T *const dst) const;
	void getDataTile(int x0, int y0, T *const dst) const
	{
		const int nMaskPts = m_Mask.size()/2;
		std::vector<int>::const_iterator x=m_Mask.begin();
		std::vector<int>::const_iterator y=m_Mask.begin()+nMaskPts;
		for(int i=0; i<nMaskPts; i++){
			int idx = (y0+y[i])%m_Height*m_Width + (x0+x[i])%m_Width;
			std::copy(m_pSource[idx], m_pSource[idx]+m_ImgDim, dst+i*m_ImgDim);
		}
	}

	void getDataInner(int x0, int y0, T *const dst) const
	{
		const int nMaskPts = m_Mask.size()/2;
		std::vector<int>::const_iterator x=m_Mask.begin();
		std::vector<int>::const_iterator y=m_Mask.begin()+nMaskPts;
		for(int i=0; i<nMaskPts; i++){
			assert((y0+y[i])<m_Height && (y0+y[i])>=0 && (x0+x[i])<m_Width && (x0+x[i])>=0);
			int idx = (y0+y[i])*m_Width + (x0+x[i]);
			std::copy(m_pSource[idx], m_pSource[idx]+m_ImgDim, dst+i*m_ImgDim);
		}
	}

};
#endif
